#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "getopt.h"

#define GOO_BOOL_TYPE 1
#define GOO_STRING_TYPE 2

getopt_t *getopt_create()
{
	getopt_t *gopt = (getopt_t*) calloc(1, sizeof(getopt_t));
	gopt->lopts = hashtable_create(32);
	gopt->sopts = hashtable_create(32);
	gopt->options = dgc_vector_create(32);
	gopt->extraargs = dgc_vector_create(16);

	return gopt;
}

void getopt_destroy(getopt_t *gopt)
{
	hashtable_destroy(gopt->lopts);
	hashtable_destroy(gopt->sopts);
	dgc_vector_destroy(gopt->options);
	dgc_vector_destroy(gopt->extraargs);

	free(gopt);
}

// returns 1 if no error
int getopt_parse(getopt_t *gopt, int argc, char *argv[], int showErrors)
{
	int okay = 1;
	int i;
	dgc_vector_t *toks = dgc_vector_create(argc+1);

	// take the input stream and chop it up into tokens
	for (i = 1; i < argc; i++) {
		char *arg = strdup(argv[i]);
		char *eq = strstr(arg, "=");

		// no equal sign? Push the whole thing.
		if (eq == NULL) {
			dgc_vector_add(toks, strdup(arg));
		} else {
			// there was an equal sign. Push the part
			// before and after the equal sign
			char *val = &eq[1];
			eq[0] = 0;
			dgc_vector_add(toks, arg);

			// if the part after the equal sign is
			// enclosed by quotation marks, strip them.
			if (val[0]=='\"') {
				int last = strlen(val) - 1;
				if (val[last]=='\"')
					val[last] = 0;
				dgc_vector_add(toks, &val[1]);
			} else {
				dgc_vector_add(toks, val);
			}
		}
	}

	// now loop over the elements and evaluate the arguments
	i = 0;
	while (i < dgc_vector_size(toks)) {

		char *tok = (char*) dgc_vector_get(toks, i);

		if (!strncmp(tok,"--", 2)) {
			char *optname = &tok[2];
			getopt_option_t *goo = (getopt_option_t*) hashtable_get(gopt->lopts, optname);
			if (goo == NULL) {
				okay = 0;
				if (showErrors) 
					printf("Unknown option --%s\n", optname);
				i++;
				continue;
			}

			if (goo->type == GOO_BOOL_TYPE) {
				if ((i+1) < dgc_vector_size(toks)) {
					char *val = (char*) dgc_vector_get(toks, i+1);
					
					if (!strcmp(val,"true")) {
						i+=2;
						goo->svalue = "true";
						continue;
					}
					if (!strcmp(val,"false")) {
						i+=2;
						goo->svalue = "false";
						continue;
					}
				}

				goo->svalue = "true";
				i++;
				continue;
			}

			if (goo->type == GOO_STRING_TYPE) {
				if ((i+1) < dgc_vector_size(toks)) {
					char *val = (char*) dgc_vector_get(toks, i+1);
					i+=2;
					
					goo->svalue = strdup(val);
					continue;
				}

				okay = 0;
				if (showErrors)
					printf("Option %s requires a string argument.\n",optname);
			}
		}

		if (!strncmp(tok,"-",1) && strncmp(tok,"--",2)) {
			int len = strlen(tok);
			int pos;
			for (pos = 1; pos < len; pos++) {
				char sopt[2];
				sopt[0] = tok[pos];
				sopt[1] = 0;
				getopt_option_t *goo = (getopt_option_t*) hashtable_get(gopt->sopts, sopt);

				if (goo==NULL) {
					okay = 0;
					if (showErrors)
						printf("Unknown option -%c\n", tok[pos]);
					i++;
					continue;
				}

				if (goo->type == GOO_BOOL_TYPE) {
					goo->svalue = "true";
					continue;
				}

				if (goo->type == GOO_STRING_TYPE) {
					if ((i+1) < dgc_vector_size(toks)) {
						char *val = (char*) dgc_vector_get(toks, i+1);
						if (val[0]=='-')
						{
							okay = 0;
							if (showErrors)
								printf("Ran out of arguments for option block %s\n", tok);
						}
						i++;
						
						goo->svalue=strdup(val);
						continue;
					}
					
					okay = 0;
					if (showErrors)
						printf("Option -%c requires a string argument.\n", tok[pos]);
				}
			} 
			i++;
			continue;
		} 

		// it's not an option-- it's an argument.
		dgc_vector_add(gopt->extraargs, tok);
		i++;
	}

	return okay;
}

void getopt_add_spacer(getopt_t *gopt, const char *s)
{
	getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
	goo->spacer = 1;
	goo->help = strdup(s);
	dgc_vector_add(gopt->options, goo);
}

void getopt_add_bool(getopt_t *gopt, char sopt, const char *lname, int def, const char *help) 
{
	char sname[2];
	sname[0] = sopt;
	sname[1] = 0;

	getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
	goo->sname=strdup(sname);
	goo->lname=strdup(lname);
	goo->svalue=strdup(def ? "true" : "false");
	goo->type=GOO_BOOL_TYPE;
	goo->help=strdup(help);
	
	hashtable_put(gopt->lopts, goo->lname, goo);
	hashtable_put(gopt->sopts, goo->sname, goo);
	dgc_vector_add(gopt->options, goo);
}

void getopt_add_int(getopt_t *gopt, char sopt, const char *lname, const char *def, const char *help)
{
	getopt_add_string(gopt, sopt, lname, def, help);
}

void getopt_add_string(getopt_t *gopt, char sopt, const char *lname, const char *def, const char *help)
{
	char sname[2];
	sname[0] = sopt;
	sname[1] = 0;

	getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
	goo->sname=strdup(sname);
	goo->lname=strdup(lname);
	goo->svalue=strdup(def);
	goo->type=GOO_STRING_TYPE;
	goo->help=strdup(help);
	
	hashtable_put(gopt->lopts, goo->lname, goo);
	hashtable_put(gopt->sopts, goo->sname, goo);
	dgc_vector_add(gopt->options, goo);
}

const char *getopt_get_string(getopt_t *gopt, const char *lname)
{
	getopt_option_t *goo = (getopt_option_t*) hashtable_get(gopt->lopts, lname);
	if (goo == NULL)
		return NULL;

	return goo->svalue;
}

int getopt_get_int(getopt_t *getopt, const char *lname)
{
	const char *v = getopt_get_string(getopt, lname);
	assert(v != NULL);
	return atoi(v);
}

int getopt_get_bool(getopt_t *getopt, const char *lname)
{
	const char *v = getopt_get_string(getopt, lname);
	assert (v!=NULL);
	return !strcmp(v, "true");
}

static int max(int a, int b)
{
	return a > b ? a : b;
}

void getopt_do_usage(getopt_t *gopt)
{
	int leftmargin=2;
	int longwidth=12;
	int valuewidth=10;

	int i;
	for (i = 0; i < dgc_vector_size(gopt->options); i++) {
		getopt_option_t *goo = (getopt_option_t*) dgc_vector_get(gopt->options, i);
		
		if (goo->spacer)
			continue;

		longwidth = max(longwidth, strlen(goo->lname));

		if (goo->type == GOO_STRING_TYPE)
			valuewidth = max(valuewidth, strlen(goo->svalue));
	}

	for (i = 0; i < dgc_vector_size(gopt->options); i++) {
		getopt_option_t *goo = (getopt_option_t*) dgc_vector_get(gopt->options, i);

		if (goo->spacer)
		{
			if (goo->help==NULL || strlen(goo->help)==0)
				printf("\n");
			else
				printf("\n%*s%s\n\n", leftmargin, "", goo->help);
			continue;
		}
		
		printf("%*s", leftmargin, "");
		
		if (goo->sname[0]==0)
			printf("     ");
		else
			printf("-%c | ", goo->sname[0]);
		
		printf("--%*s ", -longwidth, goo->lname);
		
		printf(" [ %s ]", goo->svalue);
		
		printf("%*s", (int) (valuewidth-strlen(goo->svalue)), "");
		
		printf(" %s   ", goo->help);
		printf("\n");	
	}
}
